package com.jeff.utils.security;

import com.jeff.utils.StringUtil;

/**
 * author：duff
 * version：1.0.0
 * date：2017/9/1
 * desc：
 */
public class TEAUtil {
    private static final int DELTA = 0x9E3779B9;
    private static final int LOWER_LIMIT = 48;
    private static final int UPPER_LIMIT = 59;

    private static final int CONSTANT_5 = 5;
    private static final int CONSTANT_8 = 8;
    private static final int CONSTANT_10 = 10;
    private static final int CONSTANT_16 = 16;
    private static final int CONSTANT_32 = 32;
    private static final int INIT_SUM_ROUND32 = 0xC6EF3720;
    private static final int INIT_SUM_ROUND16 = 0xE3779B90;

    private static final int[] TEA_KEY = {0x091458856, 0x0FD2845C6, 0x0B681F893, 0x02F7C49B7};

    private static final int DEFAULT_ROUND = 16;

    /**
     * 对字符串进行加密
     *
     * @param string 原始字符串
     * @return 加密后的字符串
     */
    public static String encrypt(String string) {
        if (StringUtil.isEmpty(string)) {
            return "";
        }

        int[] plain = new int[2];
        try {
            plain = preProcess(string);
        } catch (IllegalArgumentException e) {
            return "";
        }

        int[] ret = teaEncrypt(plain, TEA_KEY, DEFAULT_ROUND);

        String values = String.format("%08x%08x", ret[0], ret[1]).toLowerCase();
        StringBuilder result = new StringBuilder();

        for (int i = 0; i < values.length(); i++) {
            String buffer = values.substring(i, i + 1);
            int nValues = Integer.valueOf(buffer, CONSTANT_16);
            if (nValues >= CONSTANT_10) {
                result.append(nValues - CONSTANT_10);
            } else {
                result.append(nValues);
            }
        }

        return result.toString();
    }

    private static int[] preProcess(String string) {
        StringBuilder stringBuilder = new StringBuilder(string.trim());

        if (stringBuilder.toString().equals("0")) {
            throw new IllegalArgumentException();
        }

        boolean hasCharacter = false;
        for (int i = 0; i < stringBuilder.length(); i++) {
            int c = stringBuilder.charAt(i);
            if ((c < LOWER_LIMIT) || c > UPPER_LIMIT) {
                hasCharacter = true;
                break;
            }
        }

        if (stringBuilder.length() < CONSTANT_16) {
            while (stringBuilder.length() < CONSTANT_16) {
                stringBuilder.append("0");
            }
        }

        long l1;
        long l2;
        if (hasCharacter) {
            Long tmp1 = Long.parseLong(stringBuilder.substring(0, CONSTANT_8), SecurityUtil.RADIX_16);
            Long tmp2 = Long.parseLong(stringBuilder.substring(CONSTANT_8, CONSTANT_16), SecurityUtil.RADIX_16);
            l1 = tmp1;
            l2 = tmp2;
        } else {
            l1 = Long.valueOf(stringBuilder.substring(0, CONSTANT_8), SecurityUtil.RADIX_16);
            l2 = Long.valueOf(stringBuilder.substring(CONSTANT_8, CONSTANT_16), SecurityUtil.RADIX_16);
        }

        int[] plain = new int[2];
        plain[0] = (int) l1;
        plain[1] = (int) l2;

        return plain;
    }

    /**
     * encrypt with tea algorithm
     *
     * @param aIn    input 4-bytes
     * @param aKey   key used for encrypt
     * @param aRound loop count
     * @return encrypted output 4-bytes
     */
    public static int[] teaEncrypt(int[] aIn, int[] aKey, int aRound) {
        int y = aIn[0], z = aIn[1], sum = 0; /* set up */
        int a = aKey[0], b = aKey[1], c = aKey[2], d = aKey[3]; /* cache key */
        int i;
        for (i = 0; i < aRound; i++) { /* basic cycle start */
            sum += DELTA;

            y += ((z << 4) + a) ^ (z + sum) ^ ((z >>> CONSTANT_5) + b);
            z += ((y << 4) + c) ^ (y + sum) ^ ((y >>> CONSTANT_5) + d); /* end cycle */
        }
        int[] out = new int[2];
        out[0] = y;
        out[1] = z;
        return out;
    }

    /**
     * decrypt with tea algorithm
     *
     * @param aOut   decrypt output 4-bytes
     * @param aIn    encrypted input 4-bytes
     * @param aKey   key used for decrypt
     * @param aRound loop count
     */
    public static void teaDecrypt(int[] aOut, int[] aIn, int[] aKey, int aRound) {
        int y = aIn[0], z = aIn[1], sum;
        int a = aKey[0], b = aKey[1], c = aKey[2], d = aKey[3];

        if (aRound == CONSTANT_32) {
            sum = INIT_SUM_ROUND32;
        } else if (aRound == CONSTANT_16) {
            sum = INIT_SUM_ROUND16;
        } else {
            sum = DELTA * aRound;
        }

        for (int i = 0; i < aRound; i++) {
            z -= (((y << 4) + c) ^ (y + sum) ^ ((y >>> CONSTANT_5) + d));
            y -= (((z << 4) + a) ^ (z + sum) ^ ((z >>> CONSTANT_5) + b));
            sum -= DELTA;
        }
        aOut[0] = y;
        aOut[1] = z;
    }
}
